const {floor, random, max, min} = Math;

const game = {
  score: 0,
  scoreBoard: null,
  timer: null,
  fps: 1000 / 10,
  overText: 'GAME OVER',
  isPause: true,
  start() {
    game.isPause = false;
    stage.addSnake();
    game.updateStore();
    game.timer = setInterval(()=>{
      stage.clear();
      stage.addSnake();
      stage.addFood();
      var snake = stage.snake;
      var food = stage.food;
      if ( snake.eatFood(food) ) {
        game.score++;
        game.updateStore();
      }
      if ( snake.outOfStage(stage) || snake.eatSelf() ) {
        game.over();
        clearInterval(game.timer);
      }
    }, game.fps);
  },

  restart() {
    game.score = 0;
    game.updateStore();
    game.pause();
    stage.snake = null;
    stage.food = null;
    stage.init();
    this.start();
  },

  pause() {
    clearInterval(game.timer);
    game.timer = null;
    game.isPause = true;
  },

  updateStore() {
    this.scoreBoard.innerHTML = this.score;
  },

  toggleState(e) {
    e = e || window.event;
    e.preventDefault();
    if(!game.isPause) {
      game.pause();
    } else {
      game.start();
    }
  },

  switchDirection(dir) {
    if (game.isPause || !stage || !stage.snake ) return;
    var codeMap = {
      37: 'left',
      38: 'up',
      39: 'right',
      40: 'down',
      65: 'left',
      68: 'right',
      83: 'down',
      87: 'up'
    }
    if (codeMap.hasOwnProperty(dir)) {
      dir = codeMap[dir];
    }

    if (!['left', 'right', 'up', 'down'].includes(dir)) return;
    stage.snake.turnDirection(dir);
  },
  
  bindEvent() {
    window.addEventListener('keyup', function(e) {
      var snake = stage.snake, keyCode = e.keyCode;
      game.keyDown = true;
      switch (keyCode) {
        case 32:
          game.toggleState();
          break;
        case 78:
          game.restart();
          break;
        case 37:
        case 38:
        case 39:
        case 40:
        case 65:
        case 68:
        case 83:
        case 87:
          game.switchDirection(keyCode);
          break;
        default:
          break;
      }
    });
  },
  setup() {
    game.scoreBoard = document.querySelector('.score-board');
    stage.init();
    game.bindEvent();
  },
  over() {
    stage.clear();
    stage.snake = null;
    stage.food = null;
    game.pause();
    game.score = 0;
    stage.addText(game.overText, '30px', '#666');
  }
};

const stage = {
  width: 300,
  height: 440,
  background: '#fff',
  ctx: null,
  food: null,
  snake: null,
  addFood() {
    if (!this.snake) return;
    if (!this.food || this.food && this.food.eated ) {
      let width = 10;
      let pos = this.getPos(width);
      while( this.snake.body.some(e=>e.x === pos.x && e.y === pos.y) ) {
        pos = this.getPos();
      }
      this.food = new Food({
        ...pos,
        width,
        color: '#000'
      });
    } else {
      this.food.draw();
    }
  },
  getPos(width) {
    let
        limitW = this.width - width * 2,
        limitH = this.height - width * 2,
        x = max(width, floor(random() * this.width)),
        y = max(width, floor(random() * this.height));
        x = min(limitW, x);
        y = min(limitH, y);
        x = parseInt(x / width) * width;
        y = parseInt(y / width) * width;
    return {x, y};
  },
  addSnake() {
    if(!this.snake) {
      const width = 10;
      const maxLen = 4;
      const minLen = 2;
      const color = '#666', length = floor(random() * (maxLen - minLen) + minLen),
            maxX = stage.width - width,
            maxY = stage.height - width;
      const configs = [
        {direction: 'down', x: 0, y: 0}, //左上向下
        {direction: 'down', x: maxX, y: 0}, //右上向下
        {direction: 'up', x: 0, y: maxY}, // 左下向上
        {direction: 'up', x: maxX, y: maxY}, // 右下向上
        {direction: 'right', x: 0, y: 0}, // 左上向右
        {direction: 'right', x: 0, y: maxY}, // 左下向右
        {direction: 'left', x: maxX, y: 0}, // 右上向左
        {direction: 'left', x: maxX, y: maxY} // 右下向左
      ];
      this.snake = new Snake({...configs[floor(random() * configs.length)], width, color, length});
    } else {
      this.snake.forward();
    }
  },
  addText(text, size, color) {
    stage.ctx.font = (size || 0) + ' "Microsoft YaHei, Arial"';
    stage.ctx.fillStyle = color;
    stage.ctx.fillText(text, (stage.width - stage.ctx.measureText(text).width) / 2, (stage.height - parseInt(size)) / 2);
  },
  init() {
    if (!this.ctx) {
      const _canvas = window.canvas || document.body.appendChild( document.createElement('canvas') );
      _canvas.width = stage.width;
      _canvas.height = stage.height;
      this.ctx = _canvas.getContext('2d');
    }
    new Rectangle({x: 0, y: 0, width: this.width, height: this.height, color: this.background, lineWidth: 1, strokeColor: '#666'});
  },
  clear() {
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.init();
  }
}

class Snake {
  constructor(options) {
    options = options || {};
    this.direction = options.direction || 'right';
    this.x = options.x | 0;
    this.y = options.y | 0;
    this.width = Math.max((options.width | 0), 10);
    this.length = Math.max((options.length | 0), 2);
    this.color = options.color || '#fff';
    this.body = [];
    this.render();
  }

  render() {
    var direction = this.direction, dx, dy;
    var maxX = stage.width - this.width;
    var maxY = stage.height - this.width;
    dx = dy = this.width;
    if (direction === 'left') {
      dx = this.width; 
      this.x = this.x === 0 ? maxX : this.x;
    } else if (direction === 'right') {
      dx = -this.width;
      this.x = this.x === maxX ? 0 : this.x;
    } else if (direction === 'up') {
      dy = this.width;
      this.y = this.y === 0 ? maxY : this.y;
    } else if (direction === 'down') {
      dy = -this.width;
      this.y = this.y === maxY ? 0 : this.y;
    }
    for( let i = 0; i < this.length; i++ ) {
      this.body[i] = new SnakeBody({x: this.x + i * dx, y: this.y + i * dy, width: this.width, color: this.color});
    }
    this.update();
  }

  eatFood(food) {
    var
        head = this.head,
        tail = this.tail,
        direction = this.direction;
    if ( food.x === head.x && food.y === head.y ) {
      let x = tail.x, y = tail.y;
      food.color = this.color;
      food.draw();
      if (x > this.body[this.length - 2].x) {
        x += this.width;
      } else if (x < this.body[this.length - 2].x) {
        x -= this.width;
      }

      if (y > this.body[this.length - 2].y) {
        y += this.width;
      } else if (y < this.body[this.length - 2].y) {
        y -= this.width;
      }
      this.body.push(
        new SnakeBody({x, y, color: this.color, width: this.width})
      );
      this.update();
      return food.eated = true;
    }
    return false;
  }

  eatSelf() {
    var x = this.head.x, y = this.head.y;
    var eatedIsBody = this.body.slice(1).some(section=>section.x === x && section.y === y);
    return eatedIsBody;
  }

  turnDirection(direction) {
    if (this.direction === 'left' && direction === 'right') return;
    if (this.direction === 'right' && direction === 'left') return;
    if (this.direction === 'up' && direction === 'down') return;
    if (this.direction === 'down' && direction === 'up') return;

    this.direction = direction;
  }

  forward() {
    var
      direction = this.direction,
      body = this.body,
      tail = body.pop(),
      x = this.head.x,
      y = this.head.y,
      incr = this.width;
    if (direction === 'right') {
      x += incr;
    } else if (direction === 'left') {
      x -= incr;
    }
    if (direction === 'down') {
      y += incr
    } else if (direction === 'up') {
      y -= incr;
    }
    tail.x = x;
    tail.y = y;
    body.unshift(tail);
    body.forEach(e=>{
      e.draw();
    });
    this.update();
  }

  update() {
    this.length = this.body.length;
    this.head = this.body[0];
    this.tail = this.body[this.length - 1];
    this.x = this.head.x;
    this.y = this.head.y;
  }

  outOfStage(stage) {
    var
      head = this.head,
      width = this.width,
      stageW = stage.width,
      stageH = stage.height;

    return head.x + width > stageW ||
            head.y + width > stageH ||
            head.x < 0 || head.y < 0;
  }
}

class Food extends Square {
  constructor(options) {
    super(options);
    this.eated = false;
  }
}

class SnakeBody extends Square {
  constructor(options) {
    super(options);
  }
}

game.setup();